package top.lixunda.common.validate.core;

import lombok.extern.slf4j.Slf4j;
import top.lixunda.common.validate.validate.*;
import top.lixunda.common.validate.exception.XumValidException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Xumda
 * @version time:2019/12/9 11:46
 * <p>
 * 验证注解
 */
@SuppressWarnings({"unused", "WeakerAccess"})
@Slf4j
public class AnnotationValidator {

    private static final List<AnnotationValidate> ANNOTATION_VALIDATES;

    private static final Map<String, List<AnnotationCacheValidate>> CACHE_MAP = new ConcurrentHashMap<>(256);

    public static final AnnotationValidator DEFAULT_VALIDATOR = new AnnotationValidator();

    private static final String ORDER_METHOD_NAME = "order";

    // 添加验证器
    static {
        ANNOTATION_VALIDATES = Arrays.asList(
                new BetweenValidate(),
                new BetweenDoubleValidate(),
                new BetweenDateValidate(),
                new ContainsValidate(),
                new DateFormatValidate(),
                new DateTimeFormatValidate(),
                new DefaultValidate(),
                new DefaultToNullValidate(),
                new EmailValidate(),
                new EndWithValidate(),
                new IncludesValidate(),
                new MaxValidate(),
                new MaxDateValidate(),
                new MaxDoubleValidate(),
                new MinValidate(),
                new MinDateValidate(),
                new MinDoubleValidate(),
                new NotNullValidate(),
                new PhoneValidate(),
                new RegexValidate(),
                new StartWithValidate(),
                new TimeFormatValidate()
        );
    }

    /**
     * 验证一个属性值，在验证到第一个错误或者全部成功后返回结果
     *
     * @param obj 原对象，获取数据使用
     * @param field 属性数据
     * @return 存在错误返回错误结果，否则返回null
     */
    public ValidateError validField(Object obj, Field field) {
        log.debug("开始验证所有属性");
        // 获取属性值
        if (field == null) {
            throw new XumValidException("获取属性信息失败");
        }
        field.setAccessible(true);
        Object value;
        try {
            value = field.get(obj);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new XumValidException("获取参数值异常");
        }

        // 获取验证器
        String key = getKey(obj, field);
        List<AnnotationCacheValidate> list = CACHE_MAP.get(key);
        if (list == null) {
            log.debug("初始化属性注解验证器  key={}", key);
            list = getCacheValidList(field);
            CACHE_MAP.put(key, list);
        } else {
            log.debug("从缓存中获取了{}个注解验证器  key={}", list.size(), key);
        }

        //验证参数
        ValidateError error;
        for (AnnotationCacheValidate validate : list) {
            error = validate.valid(obj, value);
            if (error == null) {
                continue;
            }
            log.debug("发现注解参数错误  name:{}  value:{}  message：{}", field.getName(),
                    value == null ? "null" : value.toString(), error.getErrorMsg());
            return error;
        }
        log.debug("验证参数{}完成，没有发现错误", field.getName());
        return null;
    }

    /**
     * 获取注解缓存验证器信息
     *
     * @param field Field
     * @return List<AnnotationCacheValidate>
     */
    private List<AnnotationCacheValidate> getCacheValidList(Field field) {
        // 获取注解
        Annotation[] annotations = field.getAnnotations();
        if (log.isDebugEnabled()) {
            log.debug("参数{}上获取到{}个注解", field.getName(), annotations.length);
            for (Annotation a : annotations) {
                log.debug("注解是： {}", a.annotationType().getName());
            }
        }
        ValidateError error;
        List<AnnotationCacheValidate> list = new ArrayList<>(16);
        for (Annotation a : annotations) {
            for (AnnotationValidate annotationValidate : ANNOTATION_VALIDATES) {
                if (annotationValidate.support(a.annotationType())) {
                    log.debug("参数{}添加一个注解验证器: {}", field.getName(), annotationValidate.getClass().getName());
                    list.add(new AnnotationCacheValidate(annotationValidate, a, field));
                }
            }
        }

        // 获取所有的order并缓存
        Map<AnnotationCacheValidate, Double> orderList = new HashMap<>(list.size());
        double orderNum;
        for (AnnotationCacheValidate cacheValidate : list) {
            orderNum = getOrderFromAnnotation(cacheValidate.getAnnotation());
            orderList.put(cacheValidate, orderNum);
            log.debug("验证器 {} 排序号 {}", cacheValidate.getAnnotation().annotationType().getName(), orderNum);
        }
        // 排序
        log.debug("开始验证器排序");
        list.sort(Comparator.comparingDouble(orderList::get));
        if (log.isDebugEnabled()) {
            log.debug("验证器排序结束");
            for (AnnotationCacheValidate a : list) {
                log.debug(a.getAnnotation().annotationType().getName());
            }
        }
        return list;
    }

    /**
     * 获取验证器缓存的key
     *
     * @param obj   验证的对象，用于获取类的全限定名
     * @param field 属性
     * @return objName # fieldName
     */
    private String getKey(Object obj, Field field) {
        return obj.getClass().getName() + "#" + field.getName();
    }

    /**
     * 获取注解中的order字段值
     *
     * @param annotation 注解，只能是用于验证的Xum***注解并包含order字段
     * @return double  排序字段，值越小，越先验证
     */
    private double getOrderFromAnnotation(Annotation annotation) {
        Method method;
        try {
            method = annotation.annotationType().getMethod(ORDER_METHOD_NAME);
        } catch (NoSuchMethodException e) {
            throw new XumValidException("不是规定的注解，请检查代码");
        }
        try {
            return (double) method.invoke(annotation);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new XumValidException("获取注解排序参数错误");
        }
    }

    /**
     * 验证对象中所有属性
     *
     * @param obj 原对象实例，获取数据使用
     * @return 第一个发现的错误，验证通过返回null
     */
    public ValidateError validAllField(Object obj) {
        if (obj == null) {
            return null;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        if (fields == null || fields.length == 0) {
            return null;
        }
        ValidateError error;
        for (Field field : fields) {
            error = validField(obj, field);
            if (error != null) {
                return error;
            }
        }
        return null;
    }

}
